package com.kaili.common.tenant;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

import javax.sql.DataSource;

import com.alibaba.druid.filter.config.ConfigTools;
import com.kaili.common.util.CommonConstant;
import com.kaili.common.util.CommonUtil;
import com.kaili.common.util.UUIDUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;


/**
 * 数据源连接池管理
 * @Copyright: Copyright (c) 2017
 * @Company: 杭州凯立通信有限公司
 * @author zhengs
 * @version 1.0.0 2018年4月9日 下午7:19:46
 */
public class MultiDataSourcePool {
	/**
	 * 日志对象
	 */
	private static final Logger logger = LogManager.getLogger();

	/**
	 * 数据库驱动
	 */
	@Value("${spring.datasource.driverClassName}")
	private String driverClassName;

	/**
	 * 数据库连接url
	 */
	@Value("${spring.datasource.url}")
	private String url;

	/**
	 * 数据库连接用户名
	 */
	@Value("${spring.datasource.username}")
	private String username;

	/**
	 * 数据库连接密码
	 */
	@Value("${spring.datasource.password}")
	private String password;

	/**
	 * 初始化连接池大小
	 */
	@Value("${spring.datasource.initialSize}")
	private Integer initialSize;

	/**
	 * 最大连连接池大小
	 */
	@Value("${spring.datasource.maxActive}")
	private Integer maxActive;

	/**
	 * 连接池最少空闲连接数
	 */
	@Value("${spring.datasource.minIdle}")
	private Integer minIdle;

//	/**
//	 * 连接池最大空闲连接数
//	 */
//	@Value("${spring.datasource.maxIdle}")
//	private Integer maxIdle;

	/**
	 * 获取连接等待超时的时间
	 */
	@Value("${spring.datasource.maxWait}")
	private Integer maxWait;

	/**
	 * 打开PSCache
	 */
	@Value("${spring.datasource.poolPreparedStatements}")
	private boolean poolPreparedStatements;

	/**
	 * 指定每个连接上PSCache的大小
	 */
	@Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}")
	private Integer maxPoolPreparedStatementPerConnectionSize;

	/**
	 *检测连接是否有效SQL
	 */
	@Value("${spring.datasource.validationQuery}")
	private String validationQuery;

	/**
	 *检测连接是否有效SQL-超时时间
	 */
	@Value("${spring.datasource.validationQueryTimeout}")
	private Integer validationQueryTimeout;

	/**
	 *申请连接时执行SQL检测是否有效,配置为true时回降低性能
	 */
	@Value("${spring.datasource.testOnBorrow}")
	private boolean testOnBorrow;

	/**
	 *归还连接时执行SQL检测是否有效,配置为true时回降低性能
	 */
	@Value("${spring.datasource.testOnReturn}")
	private boolean testOnReturn;

	/**
	 *建议配置为true,不影响性能,申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测是否有效
	 */
	@Value("${spring.datasource.testWhileIdle}")
	private boolean testWhileIdle;

	/**
	 *配置间隔多久才进行一次检测，检测需要关闭的空闲连接，单位是毫秒
	 */
	@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
	private Integer timeBetweenEvictionRunsMillis;

	/**
	 *配置一个连接在池中最小生存的时间，单位是毫秒
	 */
	@Value("${spring.datasource.minEvictableIdleTimeMillis}")
	private Integer minEvictableIdleTimeMillis;

	/**
	 *超过时间限制是否回收
	 */
	@Value("${spring.datasource.removeAbandoned}")
	private boolean removeAbandoned;

	/**
	 *超时时间,单为为秒
	 */
	@Value("${spring.datasource.removeAbandonedTimeout}")
	private Integer removeAbandonedTimeout;

	/**
	 * 关闭abanded连接时输出错误日志
	 */
	@Value("${spring.datasource.logAbandoned}")
	private boolean logAbandoned;

	/**
	 *通过别名的方式配置扩展插件
	 */
	@Value("${spring.datasource.filters}")
	private String filters;

	/**
	 * 数据库连接实例
	 */
	private Connection conn;

	/**
	 * 数据库执行sql实例
	 */
	private Statement stmt;

	/**
	 * 数据库连接池
	 */
	private static Map<String, List<DataSource>> dataSourcePools=new ConcurrentHashMap<String,List<DataSource>>();

	/**
	 * 数据源-轮询法 记录当前位置
	 */
	private static Map<String, Integer> dataSourcePos=new ConcurrentHashMap<String,Integer>();
	
	/**
	 * 根据租户ID获取数据库连接
	 * @param key
	 * @return
	 */
	public DataSource getDataSource(String key) {
		System.out.println("Enter in getDataSource.key is "+key);
		if(null == key || key.startsWith("null")){
			try {
				return getDefaultDataSource();
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}
		
		// 1.缓存中获取数据库连接
		List<DataSource> list = dataSourcePools.get(key);
		
		// 2.如果在缓存中没有找到数据源，则去数据库中获取数据库连接
		if(list == null || list.size()==0){
			try {
				list = getDataSourceFromDB(key);
			} catch (Exception e) {
				logger.error(key + " getDataSourceFromDB is error!"+e.getMessage());
			}
			
			if(list != null && list.size()>0){
				dataSourcePools.put(key, list);
			}
		}
		
		// 3.返回数据库连接
		return getLoadBalance(list);
	}

	/**
	 * 根据负载算法从数据源集合中获取数据源
	 * @param list
	 * @return
	 */
	public DataSource getLoadBalance(List<DataSource> list){
		if(list == null || list.size() == 0){
			return null;
		}
		DataSource datasource = randomLoadBalance(list);
		
		return datasource;
	}
	
	/**
	 * 轮询法 负载算法
	 * @param list
	 * @return
	 */
	public DataSource RoundLoadBalance(String key, List<DataSource> list){
		DataSource datasource = null;
		synchronized (dataSourcePos)
		{
			Integer pos = dataSourcePos.get(key);
			if(pos == null){
				pos = 0;
				dataSourcePos.put(key, pos);
			}
			if (pos >= list.size()){
				pos = 0;
			}
			datasource = list.get(pos);
			
			dataSourcePos.put(key, pos ++);
		}
		
		return datasource;
	}
	
	/**
	 * 随机法  负载算法
	 * @param list
	 * @return
	 */
	public DataSource randomLoadBalance(List<DataSource> list){
		return list.get(new Random().nextInt(list.size()));
	}
	
	/**
	 * 根据租户ID获取数据源
	 * @param collectionId
	 * @return
	 * @throws Exception
	 */
	public List<DataSource> getDataSourceFromDB(String key) throws Exception {
		List<DataSource> list = new ArrayList<DataSource>();
		String[] array = key.split("_");
		if(array == null || array.length != 2){
			return null;
		}
        Properties props = null;
		AtomikosDataSourceBean ds = null;
		try {
			Class.forName(driverClassName);
			conn = DriverManager.getConnection(url, username, CommonUtil.druidDecrypt(password));
			stmt = conn.createStatement();

			String sql = "select driver_class_name,url,username,password,id from sys_datasource where companyId='" + array[0]+"'"+" and type='"+array[1]+"'";
			ResultSet rs = stmt.executeQuery(sql);
			while (rs.next()) {
				ds = new AtomikosDataSourceBean();

				props = initProperties();
				props.put("driverClassName", rs.getString(1));
				props.put("url", rs.getString(2));
				props.put("username", rs.getString(3));
				props.put("password", rs.getString(4));
				props.put("connectionProperties", "config.decrypt=true;publickey="+CommonConstant.DRUID_PUBLIC_KEY +";password="+rs.getString(4));

				ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
				ds.setUniqueResourceName(key+UUIDUtil.getUUId());
				ds.setMinPoolSize(initialSize);
				ds.setMaxPoolSize(maxActive);
				ds.setMaxLifetime(20000);
				try{
					ds.setLoginTimeout(30);
				}catch(Exception e){
					logger.error(e.getMessage(),e);
				}
				ds.setTestQuery(validationQuery);
				ds.setXaProperties(props);

		        list.add(ds);
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally {
			if (stmt != null) {
				stmt.close();
			}
			if (conn != null) {
				conn.close();
			}
		}

		return list;
	}

	/**
	 * 获取默认数据源
	 * @return
	 * @throws Exception
	 */
    public DataSource getDefaultDataSource() {
		AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
		ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
		ds.setUniqueResourceName("default"+UUIDUtil.getUUId());
		ds.setMinPoolSize(initialSize);
		ds.setMaxPoolSize(maxActive);
		ds.setMaxLifetime(20000);
		try{
			ds.setLoginTimeout(30);
		}catch(Exception e){
			logger.error(e.getMessage(),e);
		}
		ds.setTestQuery(validationQuery);
		ds.setXaProperties(initProperties());

		return ds;
    }

    public Properties initProperties(){
		Properties prop = new Properties();
		prop.put("url", url);
		prop.put("username", username);
		prop.put("password", password);
		prop.put("driverClassName", driverClassName);
		prop.put("initialSize", initialSize);
		prop.put("maxActive", maxActive);
		prop.put("minIdle", minIdle);
//		prop.put("maxIdle", maxIdle);
		prop.put("maxWait", maxWait);
		prop.put("poolPreparedStatements", poolPreparedStatements);
		prop.put("maxPoolPreparedStatementPerConnectionSize",maxPoolPreparedStatementPerConnectionSize);
		prop.put("validationQuery", validationQuery);
		prop.put("validationQueryTimeout", validationQueryTimeout);
		prop.put("testOnBorrow", testOnBorrow);
		prop.put("testOnReturn", testOnReturn);
		prop.put("testWhileIdle", testWhileIdle);
		prop.put("timeBetweenEvictionRunsMillis",timeBetweenEvictionRunsMillis);
		prop.put("minEvictableIdleTimeMillis",minEvictableIdleTimeMillis);
		prop.put("removeAbandoned",removeAbandoned);
		prop.put("removeAbandonedTimeout",removeAbandonedTimeout);
		prop.put("logAbandoned",logAbandoned);
		prop.put("filters", filters);
        prop.put("connectionProperties", "config.decrypt=true;publickey="+CommonConstant.DRUID_PUBLIC_KEY +";password="+password);
        prop.put("passwordCallbackClassName", CommonConstant.DRUID_PASSWORD_CALLBACK);
		return prop;
	}

	public String getDriverClassName() {
		return driverClassName;
	}

	public void setDriverClassName(String driverClassName) {
		this.driverClassName = driverClassName;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public static void main(String[] args){
//		String[] password = new String[]{"kaili123!@#"};
//		try {
//			ConfigTools.main(password);
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
		try {
			System.out.println(ConfigTools.encrypt(CommonConstant.DRUID_PRIVATE_KEY,"kaili123!@#"));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
